<template>
  <div class="layout_one">
    <div style="display: inline-block; margin-right: 50px">
      <video
        controls
        autoplay
        playsinline
        ref="videoRefA"
        width="400"
        height="300"
      />
      <h5>A</h5>
      <el-button type="primary" @click="nativeMedia">开始录制</el-button>
      <el-button type="primary" @click="stop">结束录制</el-button>
      <el-button type="primary" @click="playback">回放</el-button>
      <el-button type="primary" @click="download">下载</el-button>
    </div>
    <div style="display: inline-block">
      <video
        controls
        autoplay
        playsinline
        ref="videoRefB"
        width="400"
        height="300"
      />
      <h5>B</h5>
      <el-button type="primary" @click="call">连接</el-button>
      <el-button type="primary" @click="hangup">断开</el-button>
    </div>
    <div>
      <video
        controls
        autoplay
        playsinline
        ref="videoRefC"
        width="400"
        height="300"
      />
      <h5>C</h5>
    </div>
  </div>
</template>
      
<script lang="ts">
import { defineComponent, reactive, toRefs } from "vue";
import RecordRTC from "recordrtc";

export default defineComponent({
  name: "PeerConnection",
  setup() {
    const state = reactive({
      peerA: {} as RTCPeerConnection,
      peerB: {} as RTCPeerConnection,
      channelA: {} as RTCDataChannel,
      channelB: {} as RTCDataChannel,
      videoRefA: {} as any,
      videoRefB: {} as any,
      videoRefC: {} as any,
      messageOpen: false as boolean,
      allowCall: true as boolean,
      allowHangup: true as boolean,
      localStream: {} as MediaStream,
      receiveText: "" as string,
      blobs: [] as Array<Blob>,
      mediaRecorder: {} as MediaRecorder,
    });

    // 创建媒体流
    const createMedia = async () => {
      return await navigator.mediaDevices.getDisplayMedia({
        video: true,
        audio: true,
      });
    };

    // 播放媒体流
    const nativeMedia = async () => {
      state.localStream = await createMedia();
      state.videoRefA.srcObject = state.localStream;
      state.mediaRecorder = new MediaRecorder(state.localStream, {
        mimeType: "video/webm;codecs=vp9",
      });
      state.mediaRecorder.ondataavailable = (e) => {
        console.log("生成媒体记录");
        state.blobs.push(e.data);
      };
      state.mediaRecorder.start();
      initPeer();
    };

    // 初始化对等连接
    const initPeer = () => {
      // 创建输出端 PeerConnection
      state.peerA = new RTCPeerConnection();
      // 添加本地流
      state.localStream.getTracks().forEach((track) => {
        state.peerA.addTrack(track, state.localStream);
      });
      // 监听 A 的ICE候选信息；如果收集到，就添加给B
      state.peerA.onicecandidate = (event) => {
        if (event.candidate) {
          state.peerB.addIceCandidate(event.candidate);
        }
      };
      state.peerA.ondatachannel = (event) => {
        console.log("event", event);
        state.channelA = event.channel;
        state.channelA.binaryType = "arraybuffer";
        state.channelA.onopen = (e) => {
          console.log("channelA onopen", e);
        };
        state.channelA.onclose = (e) => {
          console.log("channelA onclose", e);
        };
        state.channelA.onmessage = (e) => {
          state.receiveText = JSON.parse(e.data).name;
          console.log("channleA onmessage", e.data);
        };
      };

      // 创建呼叫端
      state.peerB = new RTCPeerConnection();
      state.peerB.addTransceiver("video", { direction: "recvonly" });
      state.peerB.addTransceiver("audio", { direction: "recvonly" });
      // 监听是否有媒体流接入，如果有就赋值给 rtcB 的 src
      state.peerB.ontrack = (event) => {
        console.log("event-stream", event);
        state.videoRefB.srcObject = event.streams[0];
      };
      state.channelB = state.peerB.createDataChannel("messageChannel");
      console.log("state.channelB", state.channelB);
      state.channelB.binaryType = "arraybuffer";
      state.channelB.onopen = (event) => {
        console.log("channelB onopen", event);
        state.messageOpen = true;
      };
      state.channelB.onclose = (event) => {
        console.log("channelB onclose", event);
      };
      // 监听 B 的ICE候选信息；如果收集到，就添加给A
      state.peerB.onicecandidate = (event) => {
        if (event.candidate) {
          state.peerA.addIceCandidate(event.candidate);
        }
      };
      state.allowCall = false;
    };

    const onCreateOffer = async (desc: RTCSessionDescriptionInit) => {
      try {
        // 呼叫端设置本地 offer 描述
        await state.peerB.setLocalDescription(desc);
      } catch (e) {
        console.log("Offer-setLocalDescription:", e);
      }
      try {
        // 接收端设置远程 offer 描述
        await state.peerA.setRemoteDescription(desc);
      } catch (e) {
        console.log("Offer-setRemoteDescription: ", e);
      }
      try {
        // 接收端创建 answer
        const answer = await state.peerA.createAnswer();
        try {
          // 接收端设置本地 answer 描述
          await state.peerA.setLocalDescription(answer);
        } catch (e) {
          console.log("answer-setLocalDescription: ", e);
        }
        try {
          // 呼叫端设置远程 answer 描述
          await state.peerB.setRemoteDescription(answer);
        } catch (e) {
          console.log("answer-setRemoteDescription: ", e);
        }
      } catch (e) {
        console.log("crateAnswer: ", e);
      }
    };

    // 创建连接
    const call = async () => {
      // 判断是否有对应实例，没有就重新创建
      if (!state.peerA || !state.peerB) {
        initPeer();
      }
      try {
        // 创建offer
        const offer = await state.peerB.createOffer();
        await onCreateOffer(offer);
      } catch (e) {
        console.log("createOffer: ", e);
      }

      state.allowCall = true;
      state.allowHangup = false;
    };

    const hangup = () => {
      state.peerA.close();
      state.peerB.close();
      state.channelA.close();
      state.channelB.close();
      state.peerA = {} as RTCPeerConnection;
      state.peerB = {} as RTCPeerConnection;
      state.channelA = {} as RTCDataChannel;
      state.channelB = {} as RTCDataChannel;
      state.receiveText = "";
      state.allowCall = false;
      state.allowHangup = true;
    };

    /**
     * 视频回放
     */
    const playback = () => {
      const blob = new Blob(state.blobs, { type: "video/webm;codecs=vp9" });
      // injectMetadata(blob).then((newBlob: Blob) => {
      //   state.videoRefC.src = URL.createObjectURL(newBlob);
      //   state.videoRefC.play();
      // });
      state.videoRefC.src = URL.createObjectURL(blob);
      state.videoRefC.play();
    };

    /**
     * 视频下载
     */
    const download = () => {
      const blob = new Blob(state.blobs, { type: "video/webm;codecs=vp9" });
      RecordRTC.getSeekableBlob(blob, (seekableBlob: any) => {
        console.log("seekableBlob", seekableBlob);
        RecordRTC.invokeSaveAsDialog(seekableBlob, "video.mkv");
      });
      // injectMetadata(blob).then((newBlob: Blob) => {
      //   const url = URL.createObjectURL(newBlob);
      //   const a = document.createElement("a");
      //   a.href = url;
      //   a.download = "record.mkv";
      //   a.style.display = "none";
      //   a.click();
      //   a.remove();
      // });
    };

    const stop = () => {
      state.localStream.getTracks().forEach((track) => track.stop());
      hangup();
    };

    return {
      ...toRefs(state),
      nativeMedia,
      stop,
      call,
      hangup,
      playback,
      download,
    };
  },
});
</script>
      
<style scoped>
.layout_one {
  background: #fff;
  padding: 20px;
  height: 100%;
  box-shadow: 0 0 3px #e4e7ed;
}
.layout_one:hover {
  box-shadow: 0 0 5px #cdd0d6;
}
</style>